use crate::MathUtils::MathUtils;
use crate::Point::Point;
use crate::Segment::Segment;

#[derive(Debug, Clone, Copy)]
pub struct Rect {
    pub min: Point,
    pub max: Point,
}

impl Rect {
    pub fn new(min: Point, max: Point) -> Self {
        Self { min, max }
    }

    pub fn area(&self) -> f64 {
        self.width() * self.height()
    }

    pub fn width(&self) -> f64 {
        self.max.x - self.min.x
    }

    pub fn height(&self) -> f64 {
        self.max.y - self.min.y
    }

    pub fn center(&self) -> Point {
        Point::new(
            (self.min.x + self.max.x) / 2.0,
            (self.min.y + self.max.y) / 2.0,
        )
    }

    pub fn points(&self) -> Vec<Point> {
        vec![
            Point::new(self.min.x, self.min.y),
            Point::new(self.max.x, self.min.y),
            Point::new(self.max.x, self.max.y),
            Point::new(self.min.x, self.max.y),
        ]
    }

    pub fn edges(&self) -> Vec<Segment> {
        let ps = self.points();
        vec![
            Segment::new(ps[3], ps[0]),
            Segment::new(ps[0], ps[1]),
            Segment::new(ps[1], ps[2]),
            Segment::new(ps[2], ps[3]),
        ]
    }

    pub fn no_intersect(&self, other: &Rect, ep: Option<f64>) -> bool {
        MathUtils::less(self.max.x, other.min.x, ep)
            || MathUtils::greater(self.min.x, other.max.x, ep)
            || MathUtils::less(self.max.y, other.min.y, ep)
            || MathUtils::greater(self.min.y, other.max.y, ep)
    }

    pub fn contain(&self, p: &Point, ep: Option<f64>) -> bool {
        !(MathUtils::greater_equal(p.x, self.max.x, ep)
            || MathUtils::less_equal(p.x, self.min.x, ep)
            || MathUtils::greater_equal(p.y, self.max.y, ep)
            || MathUtils::less_equal(p.y, self.min.y, ep))
    }

    pub fn contain_seg(&self, seg: &Segment, ep: Option<f64>) -> bool {
        self.contain(&seg.start, ep) && self.contain(&seg.end, ep)
    }

    pub fn exclude(&self, p: &Point, ep: Option<f64>) -> bool {
        MathUtils::less(p.x, self.min.x, ep)
            || MathUtils::less(p.y, self.min.y, ep)
            || MathUtils::greater(p.y, self.max.y, ep)
            || MathUtils::greater(p.x, self.max.x, ep)
    }

    pub fn outside_touch(&self, other: &Rect, ep: Option<f64>) -> Vec<(Segment, usize)> {
        let mut tangent_s = vec![];
        if self.no_intersect(other, ep) {
            return tangent_s;
        }
        for p in other.points() {
            if self.contain(&p, ep) {
                return tangent_s;
            }
        }
        for p in self.points() {
            if other.contain(&p, ep) {
                return tangent_s;
            }
        }
        let edges = self.edges();
        let others = other.edges();
        let other_index = [2, 3, 0, 1];
        for i in 0..edges.len() {
            let edge = &edges[i];
            let index = other_index[i];
            let other_e = &others[index];
            let tangent = edge.tangent_vh(other_e, ep);
            if let Some(t) = tangent {
                if !t.is_zero(ep) {
                    tangent_s.push((t, i));
                }
            }
        }
        if tangent_s.len() > 1 {
            panic!("algorithm error!");
        }
        tangent_s
    }

    pub fn offset(&self, val: f64) -> Self {
        let new_min_x = self.min.x - val;
        let new_min_y = self.min.y - val;
        let new_max_x = self.max.x + val;
        let new_max_y = self.max.y + val;
        Self::new(
            Point::new(new_min_x, new_min_y),
            Point::new(new_max_x, new_max_y),
        )
    }

    pub fn double_collinear(&self, other: &Rect, ep: Option<f64>) -> bool {
        let mut collinear_count = 0;
        for i in 0..4 {
            let tangent = self.edges()[i].tangent_vh(&other.edges()[i], ep);
            if let Some(t) = tangent {
                if !t.is_zero(ep) {
                    collinear_count += 1;
                }
            }
        }
        if collinear_count > 1 {
            true
        } else {
            false
        }
    }
}
